Dynamic Cube Maps¶
Since the six faces of a cube map are really just six different views of a scene from the same point, it’s possible to generate a cube map automatically by rendering these six different views at runtime. This is really just a form of offscreen rendering to a texture. Instead of rendering just one 2-D texture image, though, rendering a dynamic cube map means rendering six different 2-D images, one for each face of a cube map texture. Panda3D makes this easy for you. To start rendering a dynamic cube map, simply call:
rig = NodePath('rig')
buffer = base.win.makeCubeMap(name, size, rig)
This will return an
offscreen GraphicsBuffer that will be
used to render the cube map. The three required parameters to
makeCubeMap() are:
name |
An arbitrary name to assign to the cube
map and its associated
GraphicsBuffer. This can be any
string. |
size |
The size in pixels of one side of the cube. Many graphics cards require this size to be a power of two. Some cards don’t require a power of two, but will perform very slowly if you give anything else. |
rig |
The camera rig node. This should be a
new NodePath. It will be filled in
with six cameras. See below. |
There are also additional, optional parameters to
makeCubeMap():
cameraMask |
This specifies the DrawMask that is
associated with the cube map’s cameras.
This is an advanced Panda3D feature
that can be used to hide or show
certain objects specifically for the
cube map cameras. |
toRam |
This is a boolean flag that, when True, indicates the texture image will be made available in system RAM, instead of leaving it only in texture memory. The default is False. Setting it True is slower, but may be necessary if you want to write out the generated cube map image to disk. |
Note that we passed a new
NodePath, called
rig in the above example,
to the makeCubeMap() call. This
NodePath serves as the “camera
rig”; the makeCubeMap() method will
create six cameras facing in six different directions, and attach them all to
the camera rig. Thus, you can parent this rig into your scene and move it
around as if it were a six-eyed camera. Normally, for environment maps, you
would parent the rig somewhere within your shiny object so it can look out of
the shiny object and see the things that should be reflected in it. The actual
cube map itself be retrieved with the call:
tex = buffer.getTexture()
You can apply the
texture to geometry as in the
previous example. You should use
the MWorldCubeMap mode to generate
texture coordinates for your geometry since the camera rig will have a
CompassEffect on it to keep it unrotated with respect
to render. When you are done
with the cube map, you should remove its buffer (and stop the cube map from
continuing to render) by calling:
base.graphicsEngine.removeWindow(buffer)
As a complete example, here is how we might load up a dynamic cube map environment on our teapot and move the teapot down the street to show off the dynamic reflections:
scene = loader.loadModel('bvw-f2004--streetscene/street-scene.egg')
scene.reparentTo(render)
scene.setZ(-2)
teapot = loader.loadModel('teapot.egg')
teapot.reparentTo(render)
rig = NodePath('rig')
buffer = base.win.makeCubeMap('env', 64, rig)
rig.reparentTo(teapot)
teapot.setTexGen(TextureStage.getDefault(), TexGenAttrib.MWorldCubeMap)
teapot.setTexture(buffer.getTexture())
zoom = teapot.posInterval(5, VBase3(20, 0, 0), startPos=VBase3(-20, 0, 0))
zoom.loop()
A word of caution¶
When you render a dynamic cube map, don’t forget that you are re-rendering your scene six times every frame in addition to the main frame render. If you are not careful, and if you have a complex scene, then you could easily end up reducing your frame rate by a factor of seven. It is a good idea to limit the amount of geometry that you render in the cube map. One simple way to do this is to ensure that the far plane on the cube map cameras is set relatively close in. Since all of the cube map cameras share the same lens, you can adjust the near and far plane of all of the cameras at once like this:
lens = rig.find('**/+Camera').node().getLens()
lens.setNearFar(1, 100)
It is especially
important when you are using cube maps that you structure your scene graph
hierarchically and divide it up spatially so that Panda3D’s view-frustum
culling can do an effective job of eliminating the parts of the scene that are
behind each of the six cameras. (Unfortunately, the street-scene model used in
the above example is not at all well-structured, so the example performs very
poorly on all but the highest-end hardware.) It’s also usually a good idea to
keep the cube map size (the
size parameter to
makeCubeMap()) no larger than it
absolutely has to be to get the look you want. You can also take advantage of
the DrawMask to hide things from
the cube cameras that are not likely to be important in the reflections. The
documentation for this advanced feature of Panda3D will be found in another
section of the manual (which, as of the time of this writing, has yet to be
written). Finally, you can temporarily disable the cube map rendering from
time to time if you know the environment won’t be changing for a little while.
The cube map will retain its last-rendered image. You can do this with
buffer.setActive(0). Use
buffer.setActive(1) to re-activate it.